package log

import (
	"fmt"
	"io"
	"path/filepath"
	"runtime"
	"strings"
	"time"
)

const DefaultDatetimeLayout = "2006-01-02T15:04:05.000"

type Formatter interface {
	//calldepth 调用层数，需要展示的最顶层第0层
	//w 写入接口
	//lv 日志类型
	//后面包含日志参数
	//返回日志填充的缓存
	Sprint(calldepth int, w io.Writer, lv Level, args ...any) (int, error)
	Sprintf(calldepth int, w io.Writer, lv Level, format string, args ...any) (int, error)
	Sprintr(calldepth int, w io.Writer, lv Level, r io.Reader) (int, error)
}

// properties
type Properties uint

const (
	Ldatetime Properties = 1 << iota // the date in the local time zone: 2006-01-02
	LUTC                             // if Ldate or Ltime is set, use UTC rather than the local time zone
	Llevel                           // the level of the log
	Lfunc                            // full file name and line number: gitee.com/a/b.c:23
	LineFeed                         // End with newline

	LstdFlags = Ldatetime | Llevel | Lfunc | LineFeed // initial values for the standard logger
)

func (x Properties) Enable(y Properties) bool {
	return x&y != 0
}

func (x *Properties) Remove(y Properties) {
	*x = *x - (*x & y)
}

type formatterOptions struct {
	flag      Properties //properties
	dtLayout  string     //datetime layout: 2006-01-02 15:04:05.999999
	msgprefix []any      //添加字段
}

func (o formatterOptions) Has(p Properties) bool {
	return o.flag.Enable(p)
}

var defaultFormatterOptions = formatterOptions{
	flag:     LstdFlags,
	dtLayout: DefaultDatetimeLayout,
}

type FormatterOption func(*formatterOptions)

func FProperties(p Properties) FormatterOption {
	return func(o *formatterOptions) {
		o.flag = p
	}
}

func FDatetimeLayout(layout string) FormatterOption {
	return func(o *formatterOptions) {
		t := time.Now()
		s := t.Format(layout)
		_, err := time.Parse(layout, s)
		if err == nil {
			o.dtLayout = layout
			o.flag |= Ldatetime
		}
	}
}

func FLineFeed() FormatterOption {
	return func(o *formatterOptions) {
		o.flag |= LineFeed
	}
}

func FMsgprefix(a ...any) FormatterOption {
	return func(o *formatterOptions) {
		o.msgprefix = a
	}
}

type TextFormatter struct {
	opts      formatterOptions
	msgprefix string
}

func NewTextFormatter(opts ...FormatterOption) *TextFormatter {
	f := &TextFormatter{
		opts: defaultFormatterOptions,
	}
	for _, opt := range opts {
		opt(&f.opts)
	}
	if len(f.opts.msgprefix) > 0 {
		f.msgprefix = fmt.Sprint(f.opts.msgprefix...) + " " //末尾出添加空格
	}
	return f
}

func (f *TextFormatter) write(calldepth int, w io.Writer, lv Level, text string) (int, error) {
	nr, er := f.formatHeader(w, calldepth+1, lv)
	if er != nil {
		return nr, er
	}
	lineFeed := f.opts.Has(LineFeed)
	if len(text) > 0 {
		r, er := io.WriteString(w, text)
		nr += r
		if er != nil {
			return nr, er
		}
		if lineFeed && strings.HasSuffix(text, "\n") {
			lineFeed = false
		}
	}
	if lineFeed {
		r, er := io.WriteString(w, "\n")
		nr += r
		if er != nil {
			return nr, er
		}
	}
	return nr, nil
}

func (f *TextFormatter) Sprint(calldepth int, w io.Writer, lv Level, args ...any) (int, error) {
	return f.write(calldepth+1, w, lv, fmt.Sprintln(args...))
}

func (f *TextFormatter) Sprintf(calldepth int, w io.Writer, lv Level, format string, args ...any) (int, error) {
	return f.write(calldepth+1, w, lv, fmt.Sprintf(format, args...))
}

func (f *TextFormatter) Sprintr(calldepth int, w io.Writer, lv Level, r io.Reader) (int, error) {
	nr, er := f.formatHeader(w, calldepth+1, lv)
	if er != nil {
		return nr, er
	}
	lineFeed := f.opts.Has(LineFeed)
	wr, er := io.Copy(w, r)
	if er != nil {
		return nr, er
	}
	nr += int(wr)
	if lineFeed {
		r, er := io.WriteString(w, "\n")
		nr += r
		if er != nil {
			return nr, er
		}
	}
	return nr, nil
}

// wid：最少位数
func itoa(w io.Writer, i int, wid int) (int, error) {
	// Assemble decimal in reverse order.
	var b [20]byte
	bp := len(b) - 1
	for i >= 10 || wid > 1 {
		wid--
		q := i / 10
		b[bp] = byte('0' + i%10)
		bp--
		i = q
	}
	// i < 10
	b[bp] = byte('0' + i)
	return w.Write(b[bp:])
}

// formatHeader writes log header to buf in following order (if corresponding flags are provided):
//   - datetime
//   - level
//   - func, file and line number
//   - msgprefix
func (f *TextFormatter) formatHeader(w io.Writer, calldepth int, lv Level) (int, error) {
	nr := 0
	if f.opts.Has(Ldatetime) {
		t := time.Now()
		if f.opts.Has(LUTC) {
			t = t.UTC()
		}
		r, er := io.WriteString(w, t.Format(f.opts.dtLayout))
		if er != nil {
			return 0, er
		}
		nr += r
		r, er = io.WriteString(w, " ")
		if er != nil {
			return 0, er
		}
		nr += r
	}
	if f.opts.Has(Llevel) {
		r, er := io.WriteString(w, lv.String())
		if er != nil {
			return 0, er
		}
		nr += r
		r, er = io.WriteString(w, " ")
		if er != nil {
			return 0, er
		}
		nr += r
	}
	if f.opts.Has(Lfunc) {
		pc, fn, line, ok := runtime.Caller(calldepth)
		if !ok {
			r, er := io.WriteString(w, "???:0 ")
			if er != nil {
				return 0, er
			}
			nr += r
		} else {
			fc := runtime.FuncForPC(pc)
			name := "???"
			if fc != nil {
				name = fc.Name()
			}
			r, er := io.WriteString(w, name)
			if er != nil {
				return 0, er
			}
			nr += r
			r, er = io.WriteString(w, " ")
			if er != nil {
				return 0, er
			}
			nr += r
			r, er = io.WriteString(w, filepath.Base(fn))
			if er != nil {
				return 0, er
			}
			nr += r
			r, er = io.WriteString(w, ":")
			if er != nil {
				return 0, er
			}
			nr += r
			r, er = itoa(w, line, -1)
			if er != nil {
				return 0, er
			}
			nr += r
			r, er = io.WriteString(w, " ")
			if er != nil {
				return 0, er
			}
			nr += r
		}
	}
	r, er := io.WriteString(w, f.msgprefix)
	if er != nil {
		return 0, er
	}
	nr += r
	return nr, nil
}
